/*
	Written By Pradipna Nepal
	www.pradsprojects.com

	Copyright (C) 2011 Prads Projects
	Please read COPYING.txt included along with this source code for more detail.
	If not included, see http://www.gnu.org/licenses/
*/

#include "libEmuGraphics.h"

Graphics::Graphics() {
	d3dDevice = 0;
	screen = 0;
	vbScreen = 0;

	clearScreen();
	defaultPalette[0] = usedPalette[0] = 0;				//Black/Transparent
	defaultPalette[1] = usedPalette[1] = 0;				//Black
	defaultPalette[2] = usedPalette[2] = 0x888888;		//Gray
	defaultPalette[3] = usedPalette[3] = 0xBF3932;		//Red
	defaultPalette[4] = usedPalette[4] = 0xDE7AAE;		//Pink
	defaultPalette[5] = usedPalette[5] = 0x4C3D21;		//Dark Brown
	defaultPalette[6] = usedPalette[6] = 0x905F25;		//Brown
	defaultPalette[7] = usedPalette[7] = 0xE49452;		//Orange
	defaultPalette[8] = usedPalette[8] = 0xEAD979;		//Yellow
	defaultPalette[9] = usedPalette[9] = 0x537A3B;		//Green
	defaultPalette[10] = usedPalette[10] = 0xABD54A;	//Light Green
	defaultPalette[11] = usedPalette[11] = 0x252E38;	//Dark Blue
	defaultPalette[12] = usedPalette[12] = 0x00467F;	//Blue
	defaultPalette[13] = usedPalette[13] = 0x68ABCC;	//Light Blue
	defaultPalette[14] = usedPalette[14] = 0xBCDEE4;	//Sky Blue
	defaultPalette[15] = usedPalette[15] = 0xFFFFFF;	//White

	spriteWidth = 0;
	spriteHeight = 0;
	flipX = false;
	flipY = false;
}

Graphics::~Graphics() {
	RELEASE(d3dDevice);
	RELEASE(screen);
	RELEASE(vbScreen);
}

void Graphics::createScreen() {
	TDVertices *pVData;
	vbScreen->Lock(0, 0, (void**)&pVData, 0);

	pVData[0].x = -1.6f;		pVData[0].y = -1.2f;		pVData[0].z = 0.0f; 
	pVData[0].u = 0.0f;			pVData[0].v = 0.46875f;
	
	pVData[1].x = -1.6f;		pVData[1].y = 1.2f;			pVData[1].z = 0.0f; 
	pVData[1].u = 0.0f;			pVData[1].v = 0.0f;
	
	pVData[2].x = 1.6f;			pVData[2].y = 1.2f;			pVData[2].z = 0.0f; 
	pVData[2].u = 0.625;		pVData[2].v = 0.0f;
	
	pVData[3].x = 1.6f;			pVData[3].y = 1.2f;			pVData[3].z = 0.0f; 
	pVData[3].u = 0.625;		pVData[3].v = 0.0f;
	
	pVData[4].x = 1.6f;			pVData[4].y = -1.2f;		pVData[4].z = 0.0f; 
	pVData[4].u = 0.625;		pVData[4].v = 0.46875f;
	
	pVData[5].x = -1.6f;		pVData[5].y = -1.2f;		pVData[5].z = 0.0f; 
	pVData[5].u = 0.0f;			pVData[5].v = 0.46875f;

	vbScreen->Unlock();
}

void Graphics::drawLine(unsigned char *cMem, int addrOffset, int X, int Y, unsigned char *flag) {
	if (X >= 320) return;

	for (int i = 0; i < spriteWidth; ++i) {
		if (X >= 0) {
			if ((foreground[(Y * 320) + X]) != 0) *flag |= 2;	//C Flag Raised
			foreground[(Y * 320) + X] = cMem[addrOffset] >> 4;
		}
		++X;
		if (X >= 320) return;
		if (X >= 0) {
			if ((foreground[(Y * 320) + X]) != 0) *flag |= 2;	//C Flag Raised
			foreground[(Y * 320) + X] = cMem[addrOffset] & 0xF;
		}
		++X;
		if (X >= 320) return;
		++addrOffset;
	}
}

void Graphics::drawLineFliped(unsigned char *cMem, int addrOffset, int X, int Y, unsigned char *flag) {
	if (X >= 320) return;

	for (int i = 0; i < spriteWidth; ++i) {
		if (X >= 0) {
			if ((foreground[(Y * 320) + X]) != 0) *flag |= 2;	//C Flag Raised
			foreground[(Y * 320) + X] = cMem[addrOffset] & 0xF;
		}
		++X;
		if (X >= 320) return;
		if (X >= 0) {
			if ((foreground[(Y * 320) + X]) != 0) *flag |= 2;	//C Flag Raised
			foreground[(Y * 320) + X] = cMem[addrOffset] >> 4;
		}
		++X;
		if (X >= 320) return;
		--addrOffset;
	}
}

bool Graphics::initialize(HWND hwnd, int wWidth, int wHeight) {
	IDirect3D9 *d3d = 0;

	d3d = Direct3DCreate9(D3D_SDK_VERSION);
	if (d3d == NULL) return false;

	D3DCAPS9 d3dCaps;

	if (d3d->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &d3dCaps) != D3D_OK) {
		RELEASE(d3d);
		return false;
	}
	
	//Check if Hardware vertex processing is supported or not
	int vP;
	if (d3dCaps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT)
		vP = D3DCREATE_HARDWARE_VERTEXPROCESSING;
	else
		vP = D3DCREATE_SOFTWARE_VERTEXPROCESSING;

	ZeroMemory(&d3dInfo, sizeof(d3dInfo));
	d3dInfo.BackBufferCount = 1;
	d3dInfo.BackBufferFormat = D3DFMT_X8R8G8B8;
	d3dInfo.BackBufferHeight = wHeight;
	d3dInfo.BackBufferWidth = wWidth;
	d3dInfo.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;
	d3dInfo.hDeviceWindow = hwnd;
	d3dInfo.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
	d3dInfo.SwapEffect = D3DSWAPEFFECT_DISCARD;
	d3dInfo.Windowed = TRUE;

	if (d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd, vP, &d3dInfo, &d3dDevice) != D3D_OK) {
		RELEASE(d3d);
		return false;
	}

	d3d->Release();
	
	D3DXVECTOR3 CPos(0.0f, 0.0f, -1.3f);
	D3DXVECTOR3 CAt(0.0f, 0.0f, 0.0f);
	D3DXVECTOR3 CUp(0.0f, 1.0f, 0.0f);

	D3DXMATRIX COut;
	D3DXMatrixLookAtLH(&COut, &CPos, &CAt, &CUp);
	d3dDevice->SetTransform(D3DTS_VIEW, &COut);

	//Set Projection
	D3DXMATRIX pProj;
	D3DXMatrixPerspectiveFovLH(&pProj, D3DX_PI / 2, (float)wWidth / (float)wHeight, 0.1f, 10.0f);
	d3dDevice->SetTransform(D3DTS_PROJECTION, &pProj);
	
	//Set some states
	d3dDevice->SetRenderState(D3DRS_LIGHTING, FALSE);
	d3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
	d3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
	d3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
	
	if (d3dDevice->CreateVertexBuffer(6 * sizeof(TDVertices), 0, CUSTFVF, 
									D3DPOOL_MANAGED, &vbScreen, NULL) != D3D_OK)
		return false;

	createScreen();

	if (D3DXCreateTexture(d3dDevice, 512, 512, 0, D3DUSAGE_DYNAMIC, D3DFMT_X8R8G8B8, 
							D3DPOOL_DEFAULT, &screen) != D3D_OK)
		return false;

	screen->LockRect(0, &lockedRect, 0, D3DLOCK_DISCARD);

	d3dDevice->SetFVF(CUSTFVF);
	d3dDevice->SetTexture(0, screen);
	d3dDevice->SetStreamSource(0, vbScreen, 0, sizeof(TDVertices));
	
	return true;
}

void Graphics::clearScreen() {
	memset(foreground, 0, 76800);
	background = 0;
}

bool Graphics::checkIfVBlank(unsigned int *cycle) {
	return ((*cycle >= 16667) ? true : false);
}

void Graphics::setBackground(unsigned char *index) {
	background = *index & 0xF;
}

void Graphics::setSpriteSize(unsigned char *width, unsigned char *height) {
	spriteWidth = *width;
	spriteHeight = *height;
}

void Graphics::drawSprite(unsigned char *cMem, short int X, short int Y, int addr, unsigned char *flag) {
	int tempSpriteHeight = spriteHeight;
	
	if (Y < 0) {
		Y *= -1;
		addr += Y * spriteWidth;
		tempSpriteHeight -= Y;
		Y = 0;
	}

	if (Y >= 240) return;
	if (Y + tempSpriteHeight >= 240) {
		tempSpriteHeight = 239 - Y;
	}

	if (flipY) Y += tempSpriteHeight;
	if (flipX) {
		for (int i = 0; i < tempSpriteHeight; ++i) {
			drawLineFliped(cMem, addr + (spriteWidth * i) + spriteWidth - 1, X, Y, flag);
			(flipY) ? --Y : ++Y;
		}
	} else {
		for (int i = 0; i < tempSpriteHeight; ++i) {
			drawLine(cMem, addr + (spriteWidth * i), X, Y, flag);
			(flipY) ? --Y : ++Y;
		}
	}
}

void Graphics::setFlip(bool horizontal, bool vertical) {
	flipX = horizontal;
	flipY = vertical;
}

void Graphics::loadPalette(unsigned char *cMem, int addr) {
	for (int i = 0; i < 48; i += 3) {
		usedPalette[0] = (cMem[addr + i] << 16) | (cMem[addr + i + 1] << 8) | cMem[addr + i + 2];
	}
}

void Graphics::renderToScreen() {
	for (int y = 0; y < 240; ++y) {
		for (int x = 0; x < 320; ++x) {
			if (foreground[(y * 320) + x] == 0) {
				*(int*)((char*)lockedRect.pBits + x * (lockedRect.Pitch >> 9) + y * lockedRect.Pitch) 
							= usedPalette[background];
			} else {
				*(int*)((char*)lockedRect.pBits + x * (lockedRect.Pitch >> 9) + y * lockedRect.Pitch) 
							= usedPalette[foreground[(y * 320) + x]];
			}
		}
	}

	while (frameTimer.delayFrame()) continue;

	screen->UnlockRect(0);
	d3dDevice->Clear(0, 0, D3DCLEAR_TARGET, 0xffffffff, 1.0f, 0);
	d3dDevice->BeginScene();

	d3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 2);
	
	d3dDevice->EndScene();
	d3dDevice->Present(0, 0, 0, 0);
	screen->LockRect(0, &lockedRect, 0, D3DLOCK_DISCARD);
}